package jhavelogger.server;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.InvalidPropertiesFormatException;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import jhavelogger.models.JHaveLog;
import jhavelogger.models.UnparsedLog;
import jhavelogger.parser.LogFactory;

/**
 * Listens for connections from log clients. Creates a DatabaseLoggingThread and
 * many new LogParsingThreads.
 * @author Jared Schlicht
 */
public class LogServer {
    private static BlockingQueue<JHaveLog> logQueue;
    private static Properties properties;
    
    /**
     * A read-only collection of LogFactory objects. This collection can safely
     * be passed to multiple threads.
     */
    private static Collection<LogFactory> logFactories;
    
    static
    {
	logQueue = new LinkedBlockingQueue<JHaveLog>();
	properties = new Properties();
    }
    
    /**
     * Initializes the list of files. The list of factories is read from the logserver.properties file.
     */
    private static void initializeFactories() throws InvalidPropertiesFormatException, SecurityException, NoSuchMethodException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
	String logFactoryNames = properties.getProperty("logfactories");
	if (logFactoryNames == null) {
	    throw new InvalidPropertiesFormatException("No log factories are defined in logserver.properties");
	}
	String[] factoryNames = logFactoryNames.split(",");
	
	List<LogFactory> tempFactories = new ArrayList<LogFactory>(factoryNames.length);
	
	for (String factoryName : factoryNames) {
	    Constructor<?> c = Class.forName(factoryName).getConstructor();
	    LogFactory factory = (LogFactory)c.newInstance();
	    tempFactories.add(factory);
	}
	
	logFactories = Collections.unmodifiableCollection(tempFactories);
    }
    
    /**
     * Loads the logserver.properties file into memory
     * @throws IOException if the file could not be loaded
     */
    private static void initializeProperties() throws IOException  {
	FileInputStream propertiesInput = null;
	try {
	    propertiesInput = new FileInputStream("logserver.properties");
	    properties.load(propertiesInput);
	} catch (FileNotFoundException e) {
	    throw e;
	} catch (IOException e) {
	    throw e;
	} finally {
	    if (propertiesInput != null) {
		try {
		    propertiesInput.close();
		} catch (IOException e) {
		}
	    }
	}
	
    }
    
    /**
     * Begins the log server
     * @param args command line arguments are ignored
     */
    public static void main(String[] args) {
	try {
	    initializeProperties();
	} catch (IOException e) {
	    e.printStackTrace();
	    return;
	}
	
	try {
	    initializeFactories();
	} catch (Exception e) {
	    e.printStackTrace();
	    return;
	}
	
	// Start processing logs
	new DatabaseLoggingThread(logQueue).start();
	
	int port = Integer.parseInt(properties.getProperty("port"));
	ServerSocket serverSocket = null;
	try {
	    System.out.println("Starting server on port: " + port);
	    serverSocket = new ServerSocket(port);
	    System.out.println("Now accepting connections");
	    while (true) {
		Socket client = serverSocket.accept();
		System.out.println("Accepted connection from " + client.getInetAddress().toString());
		new LogParsingThread(logFactories, client, logQueue).start();
	    }
	} catch (IOException e) {
	    e.printStackTrace();
	} finally {
	    if (serverSocket != null) {
		try {
		    serverSocket.close();
		} catch (IOException e) {
		    e.printStackTrace();
		}
	    }
	}
    }
}
